/*
@auther fenglei
*/
package xxutil

import (
	"archive/tar"
	"fmt"
	"gitee.com/fenglei-l/ops-util/xxlog"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/klauspost/compress/gzip"
)

// ExtractArchiveAuto 自动检测文件类型并解压
// 支持 .tar, .tar.gz, .tgz 格式
func ExtractArchiveAuto(archiveFile, destDir string) error {
	// 获取文件扩展名
	ext := strings.ToLower(filepath.Ext(archiveFile))

	// 处理 .tgz 特殊情况
	if ext == ".tgz" {
		return extractTarGzWith(archiveFile, destDir)
	}
	// 处理 .tar.gz 情况
	if strings.HasSuffix(strings.ToLower(archiveFile), ".tar.gz") {
		return extractTarGzWith(archiveFile, destDir)
	}
	// 处理 .tar 情况
	if ext == ".tar" {
		return extractTarFile(archiveFile, destDir)
	}
	// 尝试检测文件类型
	file, err := os.Open(archiveFile)
	if err != nil {
		return fmt.Errorf("打开文件失败: %w", err)
	}
	defer file.Close()

	// 读取文件头部
	magicBytes := make([]byte, 4)
	_, err = file.Read(magicBytes)
	if err != nil {
		return fmt.Errorf("读取文件头部失败: %w", err)
	}

	// 重置文件指针
	_, err = file.Seek(0, 0)
	if err != nil {
		return fmt.Errorf("重置文件指针失败: %w", err)
	}

	// 检测是否为 gzip
	if magicBytes[0] == 0x1f && magicBytes[1] == 0x8b {
		return extractTarGzWith(archiveFile, destDir)
	}
	// 尝试作为纯 TAR 文件解压
	return extractTarFile(archiveFile, destDir)
}

// ExtractTarGzWithKlauspost 使用 klauspost/compress 库解压 tar.gz 文件
func extractTarGzWith(tarGzFile, destDir string) error {
	// 打开源文件
	file, err := os.Open(tarGzFile)
	if err != nil {
		return fmt.Errorf("打开文件失败: %w", err)
	}
	defer file.Close()
	// 检查文件魔数，判断是否为 gzip 格式
	magicBytes := make([]byte, 2)
	_, err = file.Read(magicBytes)
	if err != nil {
		return fmt.Errorf("读取文件头部失败: %w", err)
	}
	// 重置文件指针到开头
	_, err = file.Seek(0, 0)
	if err != nil {
		return fmt.Errorf("重置文件指针失败: %w", err)
	}
	var tarReader *tar.Reader
	// gzip 文件的魔数是 0x1f 0x8b
	if magicBytes[0] == 0x1f && magicBytes[1] == 0x8b {
		// 使用 klauspost 的 gzip 解压器
		gzReader, err := gzip.NewReader(file)
		if err != nil {
			return fmt.Errorf("创建 gzip 读取器失败: %w", err)
		}
		defer gzReader.Close()
		tarReader = tar.NewReader(gzReader)
	} else {
		// 尝试作为纯 TAR 文件处理
		tarReader = tar.NewReader(file)
	}

	// 确保目标目录存在
	if err := os.MkdirAll(destDir, 0755); err != nil {
		return fmt.Errorf("创建目标目录失败: %w", err)
	}

	// 解压文件
	extractedFiles := 0
	for {
		header, err := tarReader.Next()
		if err == io.EOF {
			break // 文件结束
		}
		if err != nil {
			if extractedFiles == 0 {
				// 如果一个文件都没解压出来，可能不是 TAR 格式
				return fmt.Errorf("读取 tar 头部失败，可能不是有效的 TAR 或 TAR.GZ 文件: %w", err)
			}
			continue
		}

		// 构建完整的目标路径
		path := filepath.Join(destDir, header.Name)

		// 安全检查：确保路径不超出目标目录
		if !strings.HasPrefix(path, filepath.Clean(destDir)+string(os.PathSeparator)) {
			return fmt.Errorf("不安全的路径: %s", header.Name)
		}

		switch header.Typeflag {
		case tar.TypeDir:
			// 创建目录
			if err := os.MkdirAll(path, 0755); err != nil {
				return fmt.Errorf("创建目录失败 %s: %w", path, err)
			}
		case tar.TypeReg:
			// 确保父目录存在
			if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
				return fmt.Errorf("创建目录失败 %s: %w", filepath.Dir(path), err)
			}

			// 创建文件
			outFile, err := os.Create(path)
			if err != nil {
				return fmt.Errorf("创建文件失败 %s: %w", path, err)
			}

			// 设置文件权限
			if err := os.Chmod(path, os.FileMode(header.Mode)); err != nil {
				outFile.Close()
				return fmt.Errorf("设置文件权限失败: %w", err)
			}

			// 复制内容 - 使用缓冲以提高性能
			if _, err := io.Copy(outFile, tarReader); err != nil {
				outFile.Close()
				return fmt.Errorf("写入文件失败: %w", err)
			}
			outFile.Close()
			extractedFiles++
		}
	}

	if extractedFiles == 0 {
		return fmt.Errorf("未能解压任何文件，可能不是有效的 TAR 或 TAR.GZ 文件")
	}
	return nil
}

// ExtractTarFile 解压纯 TAR 文件（未压缩）
func extractTarFile(tarFile, destDir string) error {
	// 打开源文件
	file, err := os.Open(tarFile)
	if err != nil {
		return fmt.Errorf("打开文件失败: %w", err)
	}
	defer file.Close()
	// 使用标准库的 tar 读取器
	tarReader := tar.NewReader(file)
	// 确保目标目录存在
	if err := os.MkdirAll(destDir, 0755); err != nil {
		return fmt.Errorf("创建目标目录失败: %w", err)
	}
	// 解压文件
	extractedFiles := 0
	for {
		header, err := tarReader.Next()
		if err == io.EOF {
			break // 文件结束
		}
		if err != nil {
			if extractedFiles == 0 {
				return fmt.Errorf("读取 tar 头部失败，可能不是有效的 TAR 文件: %w", err)
			}
			xxlog.ErrorUp366(fmt.Errorf("警告: 读取某个文件失败: %v", err))
			continue
		}
		// 构建完整的目标路径
		path := filepath.Join(destDir, header.Name)
		// 安全检查：确保路径不超出目标目录
		if !strings.HasPrefix(path, filepath.Clean(destDir)+string(os.PathSeparator)) {
			return fmt.Errorf("不安全的路径: %s", header.Name)
		}
		switch header.Typeflag {
		case tar.TypeDir:
			// 创建目录
			if err := os.MkdirAll(path, 0755); err != nil {
				return fmt.Errorf("创建目录失败 %s: %w", path, err)
			}
		case tar.TypeReg:
			// 确保父目录存在
			if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
				return fmt.Errorf("创建目录失败 %s: %w", filepath.Dir(path), err)
			}

			// 创建文件
			outFile, err := os.Create(path)
			if err != nil {
				return fmt.Errorf("创建文件失败 %s: %w", path, err)
			}

			// 复制内容
			if _, err := io.Copy(outFile, tarReader); err != nil {
				outFile.Close()
				return fmt.Errorf("写入文件失败: %w", err)
			}
			outFile.Close()
			extractedFiles++
		}
	}

	if extractedFiles == 0 {
		return fmt.Errorf("未能解压任何文件，可能不是有效的 TAR 文件")
	}

	xxlog.InfoUp366("成功解压:", extractedFiles, " 个文件到:", destDir)
	return nil
}
